﻿using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using StackExchange.Redis;
using WeDonekRpc.CacheClient.Config;
using WeDonekRpc.CacheClient.Helper;
using WeDonekRpc.Helper;

namespace WeDonekRpc.CacheClient.Redis
{
    internal class RedisClient : System.IDisposable
    {
        private static RedisConfig _Config;

        public static void Init (RedisConfig config)
        {
            ConnectionMultiplexer.SetFeatureFlag("preventthreadtheft", true);
            _Config = config;
        }
        private ConnectionMultiplexer _Client = null;
        private int _ConError = 0;
        private readonly CommandMap _CommandMap;
        private readonly string _Name;
        private readonly bool _useHight = false;

        public IDatabase DataBase
        {
            get;
            private set;
        }
        public bool IsConnected => this._Client.IsConnected;

        public event Action RetryLoad;
        public RedisClient (CommandMap command, string name, bool useHight)
        {
            this._Name = name;
            this._CommandMap = command;
            this._useHight = useHight;
            this._InitClient(null);
        }
        public RedisClient ()
        {
            this._Name = _Config.CommandMap.ToString();
            this._useHight = false;
            this._CommandMap = _GetCommandMap(_Config.CommandMap);
            this._InitClient(null);
        }
        private void _InitClient (object state)
        {
            try
            {
                if (this._Client != null && this._Client.IsConnected)
                {
                    this.DataBase = this._Client.GetDatabase(_Config.DefaultDatabase);
                    this.RetryLoad();
                    _ = Interlocked.Exchange(ref this._ConError, 0);
                    return;
                }
                this._Client?.Dispose();
                this._Client = _GetRedisClient(this._CommandMap, this._Name, this._useHight);
                this._Client.ConnectionFailed += this._Client_ConnectionFailed;
                if (this._Client.IsConnected)
                {
                    this.DataBase = this._Client.GetDatabase(_Config.DefaultDatabase);
                    _ = Interlocked.Exchange(ref this._ConError, 0);
                    if (this.RetryLoad != null)
                    {
                        this.RetryLoad();
                    }
                }
                else
                {
                    this._RetryCon();
                }
            }
            catch (Exception e)
            {
                this._RetryCon();
                new ErrorException(e).Save("Redis");
            }
        }
        public IDatabase GetDatabase (int db)
        {
            if (db == -1 || db == _Config.DefaultDatabase)
            {
                return this.DataBase;
            }
            return this._Client.GetDatabase(db);
        }
        public ISubscriber GetSubscriber ()
        {
            return this._Client.GetSubscriber();
        }
        public IServer[] GetServers ()
        {
            return this._Client.GetServers();
        }
        public void Dispose ()
        {
            this._Client.Dispose();
        }
        private void _RetryCon ()
        {
            int num = Interlocked.Increment(ref this._ConError);
            int time = 100 * ( num % 11 );
            _ = new Timer(new TimerCallback(this._InitClient), null, time, Timeout.Infinite);
        }
        private void _Client_ConnectionFailed (object sender, ConnectionFailedEventArgs e)
        {
            this._RetryCon();
            if (LogSystem.CheckIsRecord(LogGrade.ERROR))
            {
                ErrorException error = new ErrorException(e.Exception);
                error.Args.Add("PhysicalName", e.ToString());
                error.Args.Add("ConnectionType", e.ConnectionType.ToString());
                error.Args.Add("FailureType", e.FailureType.ToString());
                error.Args.Add("EndPoint", e.EndPoint.ToString());
                error.Save("Redis");
            }
        }
        #region 静态方法
        private static bool _Config_CertificateValidation (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }
        private static ConnectionMultiplexer _GetRedisClient (CommandMap map, string name, bool useHight)
        {
            ConfigurationOptions config;
            if (!_Config.ConString.IsNull())
            {
                config = ConfigurationOptions.Parse(_Config.ConString);
            }
            else
            {
                config = new ConfigurationOptions
                {
                    AbortOnConnectFail = true,
                    DefaultDatabase = _Config.DefaultDatabase,
                    ClientName = RpcCacheService.FormatKey(name),
                    AllowAdmin = _Config.AllowAdmin,
                    CommandMap = map,
                    KeepAlive = _Config.KeepAlive,
                    ConnectRetry = _Config.ConnectRetry,
                    SyncTimeout = _Config.SyncTimeout,
                    ConnectTimeout = _Config.ConnectTimeout,
                    User = _Config.UserName,
                    Password = _Config.Pwd,
                    AsyncTimeout = _Config.AsyncTimeout,
                    CheckCertificateRevocation = _Config.CheckCertificateRevocation,
                    ServiceName = _Config.ServiceName,
                    SetClientLibrary = _Config.SetClientLibrary,
                    ConfigCheckSeconds = _Config.ConfigCheckSeconds,
                    ConfigurationChannel = _Config.ConfigurationChannel,
                    BacklogPolicy = _Config.BacklogPolicy == RedisFailFast.FailFast ? BacklogPolicy.FailFast : BacklogPolicy.Default,
                    HeartbeatInterval = TimeSpan.FromSeconds(_Config.HeartbeatInterval),
                    IncludeDetailInExceptions = _Config.IncludeDetailInExceptions,
                    IncludePerformanceCountersInExceptions = _Config.IncludePerformanceCountersInExceptions,
                    LibraryName = _Config.LibraryName,
                    TieBreaker = _Config.TieBreaker,
                    ResolveDns = _Config.ResolveDns
                };
                _Config.ServerIp.ForEach(a =>
                {
                    config.EndPoints.Add(CacheHelper.GetServer(a, 6379));
                });
            }
            config.CertificateValidation += _Config_CertificateValidation;
            config.SocketManager = new SocketManager(name, useHight);
            ConnectionMultiplexer client = ConnectionMultiplexer.Connect(config);
            client.ErrorMessage += _Client_ErrorMessage;
            client.ConnectionRestored += _Client_ConnectionRestored;
            client.InternalError += _Client_InternalError;
            return client;
        }
        /// <summary>
        /// 每当发生内部错误时引发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void _Client_InternalError (object sender, InternalErrorEventArgs e)
        {
            if (LogSystem.CheckIsRecord(LogGrade.ERROR))
            {
                ErrorException error = new ErrorException(e.Exception);
                error.Args.Add("PhysicalName", e.ToString());
                error.Args.Add("ConnectionType", e.ConnectionType.ToString());
                error.Args.Add("Origin", e.Origin);
                error.Args.Add("EndPoint", e.EndPoint.ToString());
                error.Save("Redis");
            }
        }
        /// <summary>
        /// 在建立物理连接时引发
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void _Client_ConnectionRestored (object sender, ConnectionFailedEventArgs e)
        {
            if (LogSystem.CheckIsRecord(LogGrade.ERROR))
            {
                ErrorException error = e.Exception != null ? new ErrorException(e.Exception, "redis.connection.error") : new ErrorException("redis.connection.error");
                error.Args.Add("PhysicalName", e.ToString());
                error.Args.Add("ConnectionType", e.ConnectionType.ToString());
                error.Args.Add("FailureType", e.FailureType.ToString());
                error.Args.Add("EndPoint", e.EndPoint.ToString());
                error.Save("Redis");
            }
        }
        /// <summary>
        /// 服务器回复错误消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void _Client_ErrorMessage (object sender, RedisErrorEventArgs e)
        {
            if (LogSystem.CheckIsRecord(LogGrade.ERROR))
            {
                new ErrorLog("redis.msg.error", "Redis回复错误消息!", "Redis")
                {
                        { "Message",e.Message},
                        { "EndPoint",e.EndPoint}
                }.Save();
            }
        }
        private static CommandMap _GetCommandMap (RedisCommandMap map)
        {
            switch (map)
            {
                case RedisCommandMap.Twemproxy:
                    return CommandMap.Twemproxy;
                case RedisCommandMap.Envoyproxy:
                    return CommandMap.Envoyproxy;
                case RedisCommandMap.SSDB:
                    return CommandMap.SSDB;
                case RedisCommandMap.Sentinel:
                    return CommandMap.Sentinel;
                default:
                    return CommandMap.Default;
            }
        }
        #endregion
    }
}
